Applications written in unsafe languages like C and C++ are vulnerable to many types of errors. In particular, these programs are vulnerable to memory management errors, such as buffer overflows, dangling pointers and reads of uninitialized data. Such errors can lead to program crashes, security vulnerabilities and unpredictable behavior. While many safe languages are now in wide use, a good number of installed software applications in use today are written in unsafe languages, such as C and C++. These languages allow programmers to maximize performance by providing greater control over such operations as memory allocation, but they are also error-prone.
Memory management errors at runtime are especially troublesome. Dynamic memory allocation is the allocation of memory storage for use in a computer program during the runtime of that program. It is a way of distributing ownership of limited memory resources among many pieces of data and code. A dynamically allocated object remains allocated until it is de-allocated explicitly, either by the programmer or by a garbage collector. In heap-based dynamic memory allocation, memory is allocated from a large pool of unused memory area called the heap. The size of the memory allocation can be determined at runtime, and the lifetime of the allocation is not dependent on the current procedure or stack frame. The region of allocated memory is accessed indirectly, usually via a reference.
The basic functions of heap memory management by a memory allocator in the C language, for instance, includes the function Malloc( ) for allocating an address on the heap to an object and the function Free( ) for freeing the object (in other words, de-allocating the memory on the heap associated with the object). There are some well-known runtime memory management errors, which can be categorized as follows:
Dangling pointers: If a live object is freed prematurely, the memory allocator may overwrite its contents on the heap with a new object or heap metadata.
Buffer overflows: Out-of-bound writes to heap can overwrite live objects on the heap thus, corrupting their contents.
Heap metadata overwrites: If heap metadata is stored too near heap objects, it can also be corrupted by buffer overflows.
Uninitialized reads: Reading values from newly-allocated memory leads to undefined behavior.
Invalid frees: Passing illegal addresses to Free( ) can corrupt the heap or lead to undefined behaviour.
Double frees: Repeated calls to Free ( ) of objects that have already been freed undermine the integrity of freelist-based allocators.
Many of the methods for improved software robustness generally seek to pinpoint the location of such errors in a program by extensive testing and then fixing the identified errors. The effectiveness of this paradigm, however, is subject to the effectiveness of the testing regime and requires changes to the code. Moreover, even extensively tested programs can fail in the field once they are deployed.
Thus, error tolerant executions of programs with memory management errors are desirable. Execution contexts that result in error tolerant executions can be identified based on the observation that a runtime system's scheme for allocating memory at runtime can vary significantly and, yet, maintain identical execution semantics with respect to a correct execution of the program. Thus, if there is an error in a program, some of these allocation schemes will result in incorrect execution of the program, while others do not. For instance, if there is a buffer overrun, then writing to the memory beyond the array may overwrite other important data. Some execution contexts are tolerant of such errors. For instance, if it so happens that the locations at the end of the array are not being used for other purposes by the program, no overwrites are caused. As a result, some error-tolerant execution contexts will allow programs with errors such as buffer overruns to execute correctly to completion despite the errors, while others do not.
Based on this observation, for a given program, it is desirable to improve software robustness by seeking an execution context in which, despite any errors, the program will terminate correctly.